package com.jeesite.modules.storage.util;

import com.alibaba.fastjson.JSON;
import com.jeesite.common.config.Global;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;

public class WeChatUtil {

    private static String publicAppId = Global.getConfig("storage.wechat.public.appId");
    private static String publicSecret = Global.getConfig("storage.wechat.public.secret");
    public static String openAppId = Global.getConfig("storage.wechat.open.appId");
    private static String mchId = Global.getConfig("storage.wechat.mchId");
    private static String key = Global.getConfig("storage.wechat.key");
    private static String certPath = Global.getConfig("storage.wechat.certPath");

    private static String payNotify = Global.getConfig("storage.wechat.notify.pay");
    private static String refundNotify = Global.getConfig("storage.wechat.notify.refund");
    private static String transferNotify = Global.getConfig("storage.wechat.notify.transfer");

    private static String payUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    private static String transferUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
    private static String refundUrl="https://api.mch.weixin.qq.com/secapi/pay/refund";
    private static String hbUrl="https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key : data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        } catch (Exception ex) {
        }
        return output;
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, String signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put("sign", sign);
        return mapToXml(data);
    }

    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key    API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key, String signType) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey("sign")) {
            return false;
        }
        String sign = data.get("sign");
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, String signType) throws Exception {
        if (!data.containsKey("sign")) {
            return false;
        }
        String sign = data.get("sign");
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, String signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals("sign")) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空，则不参与签名

                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if ("MD5".equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if ("HMACSHA256".equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        String res = "";
        for (int i = 0; i < 16; i++) {
            Random rd = new Random();
            res += chars.charAt(rd.nextInt(chars.length() - 1));
        }
        return res;
    }

    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 获取当前时间戳，单位秒
     *
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 获取当前时间戳，单位毫秒
     *
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);
        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }

    public static InputStream getCertStream() throws IOException {
        File file = new File(certPath);
        InputStream certStream = new FileInputStream(file);
        byte[] certData = new byte[(int) file.length()];
        certStream.read(certData);
        return certStream;
    }

    private static String requestOnce(String url, String data, boolean useCert) throws Exception {
        BasicHttpClientConnectionManager connManager;
        if (useCert) {
            // 证书
            char[] password = mchId.toCharArray();
            File file = new File(certPath);
            InputStream certStream = new FileInputStream(file);
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(certStream, password);

            // 实例化密钥库 & 初始化密钥工厂
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, password);

            // 创建 SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());

            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    sslContext,
                    new String[]{"TLSv1"},
                    null,
                    new DefaultHostnameVerifier());

            connManager = new BasicHttpClientConnectionManager(
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", sslConnectionSocketFactory)
                            .build(),
                    null,
                    null,
                    null
            );
        } else {
            connManager = new BasicHttpClientConnectionManager(
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", SSLConnectionSocketFactory.getSocketFactory())
                            .build(),
                    null,
                    null,
                    null
            );
        }

        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .build();

        HttpPost httpPost = new HttpPost(url);

        RequestConfig requestConfig = RequestConfig.custom().build();
        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity(data, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);

        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        return EntityUtils.toString(httpEntity, "UTF-8");
    }

    /*
     * 获取授权Url
     * */
    public static String buildAuthorizeUrl(String redirectUri,String scope){
        return "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+publicAppId+"&redirect_uri="+redirectUri+"&response_type=code&scope="+scope+"&state=STATE#wechat_redirect";
    }

    /*
     * 获取OpenId
     * */
    public static String getOpenIdByCode(String code) throws IOException {
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+publicAppId+"&secret="+publicSecret+"&code="+code+"&grant_type=authorization_code";
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000).setConnectionRequestTimeout(35000).setSocketTimeout(60000).build();
        httpGet.setConfig(requestConfig);
        httpGet.setHeader("Content-type", "application/json");
        httpGet.setHeader("DataEncoding", "UTF-8");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        HttpEntity entity = response.getEntity();
        Map map = JSON.parseObject(EntityUtils.toString(entity, "UTF-8"), Map.class);
        return map.containsKey("openid") ? String.valueOf(map.get("openid")):"";
    }

    /*
    * APP发起支付
    * @return {"appid":"wxc103353db679748a","partnerid":"1523251831","prepayid":"wx04145538741070c0db8d64551268161200","noncestr":"5e39156abd6ed","timestamp":1580799338,"package":"Sign=WXPay","sign":"19D5F9E8B9901393FA1698A744ABED87"}
    * */
    public static SortedMap<String, String> createAppPay(String outTradeNo,String body,Double totalFee) throws Exception{
        totalFee = 0.01d;
        Map<String, String> reqData = new HashMap<>();
        reqData.put("appid", openAppId);
        reqData.put("mch_id", mchId);
        reqData.put("nonce_str", generateNonceStr()); //随机字符串
        reqData.put("body", body); //商品描述
        reqData.put("out_trade_no", outTradeNo);//商户订单号
        reqData.put("total_fee", String.valueOf( (new Double(totalFee * 100)).intValue()));
        reqData.put("trade_type", "APP");
        reqData.put("notify_url", payNotify);
        reqData.put("sign_type", "MD5");
        String resultStr = requestOnce(payUrl, generateSignedXml(reqData, key, "MD5"), false);
        Map<String, String> resultDate = WeChatUtil.xmlToMap(resultStr);
        if (!resultDate.get("return_code").equals("SUCCESS") || !resultDate.get("result_code").equals("SUCCESS")) {
            return new TreeMap<>();
        }
        SortedMap<String, String> payParamSortedMap = new TreeMap<>();
        payParamSortedMap.put("appid", openAppId);
        payParamSortedMap.put("partnerid", mchId);
        payParamSortedMap.put("prepayid", resultDate.get("prepay_id"));
        payParamSortedMap.put("package", "Sign=WXPay");
        payParamSortedMap.put("noncestr", WeChatUtil.generateNonceStr());
        //本来生成的时间戳是13位，但是ios必须是10位，所以截取了一下
        payParamSortedMap.put("timestamp", String.valueOf(System.currentTimeMillis()).substring(0, 10));
        payParamSortedMap.put("sign", generateSignature(payParamSortedMap, key, "MD5"));
        return payParamSortedMap;
    }

    /*
    * H5发起支付
    * @return {\"appid\":\"wxfab52b66b726a6d9\",\"timestamp\":\"1580786815\",\"noncestr\":\"5e38e47f3a5ac\",\"package\":\"prepay_id=wx0411265521123645faaabd5b1644682600\",\"signtype\":\"MD5\",\"paysign\":\"77529F08D5B461B373D880C470F696C3\"}
    * */
    public static SortedMap<String, String> createJSAPIPay(String outTradeNo,String openId,String body,Double totalFee) throws Exception {
        totalFee = 0.01d;
        Map<String, String> reqData = new HashMap<>();
        reqData.put("appid", publicAppId);
        reqData.put("mch_id", mchId);
        reqData.put("nonce_str", WeChatUtil.generateNonceStr()); //随机字符串
        reqData.put("body", body); //商品描述
        reqData.put("out_trade_no", outTradeNo);//商户订单号
        reqData.put("total_fee", String.valueOf( (new Double(totalFee * 100)).intValue()));
        reqData.put("openid", openId);//用户openid
        reqData.put("trade_type", "JSAPI");
        reqData.put("notify_url", payNotify);
        String resultStr = requestOnce(payUrl, generateSignedXml(reqData, key, "MD5"), false);
        Map<String, String> resultDate = WeChatUtil.xmlToMap(resultStr);
        if (!resultDate.get("return_code").equals("SUCCESS") || !resultDate.get("result_code").equals("SUCCESS")) {
            return new TreeMap<>();
        }
        SortedMap<String, String> payParamSortedMap = new TreeMap<String, String>();
        payParamSortedMap.put("appId", publicAppId);
        payParamSortedMap.put("timeStamp", String.valueOf(System.currentTimeMillis()).toString().substring(0, 10));
        payParamSortedMap.put("nonceStr", WeChatUtil.generateNonceStr());
        payParamSortedMap.put("package", "prepay_id=" + resultDate.get("prepay_id"));

//        payParamSortedMap.put("partner_id", mchId);
//        payParamSortedMap.put("prepayid", resultDate.get("prepay_id"));
        payParamSortedMap.put("signType", "MD5");
        //本来生成的时间戳是13位，但是ios必须是10位，所以截取了一下
        payParamSortedMap.put("paySign", WeChatUtil.generateSignature(payParamSortedMap, key, "MD5"));
        return payParamSortedMap;
    }

    /*
    * 网页支付
    * */
    public static void createH5pay() throws Exception {
        Map<String, String> reqData = new HashMap<>();
        reqData.put("appid", publicAppId);
        reqData.put("mch_id", mchId);
        reqData.put("nonce_str", generateNonceStr()); //随机字符串
        reqData.put("body", "测试商品"); //商品描述
        reqData.put("out_trade_no", "TestWx003");//商户订单号
        reqData.put("total_fee", "1");
        reqData.put("trade_type", "MWEB");
        reqData.put("notify_url", payNotify);
        reqData.put("scene_info", "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"https://pay.qq.com\",\"wap_name\": \"腾讯充值\"}}");
//        该字段用于上报支付的场景信息,针对H5支付有以下三种场景,请根据对应场景上报,H5支付不建议在APP端使用，针对场景1，2请接入APP支付，不然可能会出现兼容性问题
//        1，IOS移动应用
//        {"h5_info": //h5支付固定传"h5_info"
//            {"type": "",  //场景类型
//                    "app_name": "",  //应用名
//                    "bundle_id": ""  //bundle_id
//            }
//        }
//
//        2，安卓移动应用
//        {"h5_info": //h5支付固定传"h5_info"
//            {"type": "",  //场景类型
//                    "app_name": "",  //应用名
//                    "package_name": ""  //包名
//            }
//        }
//
//        3，WAP网站应用
//        {"h5_info": //h5支付固定传"h5_info"
//            {"type": "",  //场景类型
//                    "wap_url": "",//WAP网站URL地址
//                    "wap_name": ""  //WAP 网站名
//            }
//        }
        String str = generateSignedXml(reqData, key, "MD5");
        String resultStr = requestOnce(payUrl, str, false);
        Map<String, String> resultDate = xmlToMap(resultStr);
        if (resultDate.get("return_code").equals("SUCCESS") && resultDate.get("result_code").equals("SUCCESS")) {
            SortedMap<String, String> parameterMap2 = new TreeMap<String, String>();
            parameterMap2.put("appid", publicAppId);
            parameterMap2.put("partnerid", mchId);
            parameterMap2.put("mweb_url", resultDate.get("mweb_url"));//mweb_url为拉起微信支付收银台的中间页面，可通过访问该url来拉起微信客户端，完成支付,mweb_url的有效期为5分钟。
            parameterMap2.put("prepayid", resultDate.get("prepay_id"));
            parameterMap2.put("package", "Sign=WXPay");
            parameterMap2.put("noncestr", generateNonceStr());
            //本来生成的时间戳是13位，但是ios必须是10位，所以截取了一下
            parameterMap2.put("timestamp", String.valueOf(System.currentTimeMillis()).toString().substring(0, 10));
            String sign2 = generateSignature(parameterMap2, key, "MD5");
            parameterMap2.put("sign", sign2);//返回这对象parameterMap2内容送到ios调用就可以了
        } else {
            // 错误处理
        }
    }

    /*
     * 退款
     * */
    public static Boolean refund(String orderId, String appId,String transactionId,Double totalFee) throws Exception {
        Map<String, String> reqData = new HashMap<>();
        reqData.put("appid", appId);
        reqData.put("mch_id", mchId);
        reqData.put("nonce_str", generateNonceStr()); //随机字符串
        reqData.put("transaction_id", transactionId);//微信返回的订单号,商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。transaction_id、out_trade_no二选一，如果同时存在优先级：transaction_id> out_trade_no
        reqData.put("out_refund_no", WebUtil.getTradeNoByOrderId(orderId)); //商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一退款单号多次请求只退一笔。
        //reqData.put("out_trade_no", ""); //商户系统内部的单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，发起支付的订单号。
        reqData.put("refund_fee", String.valueOf( (new Double(totalFee * 100)).intValue()));
        reqData.put("total_fee",String.valueOf( (new Double(totalFee * 100)).intValue()));
        reqData.put("notify_url", refundNotify);
        String str = generateSignedXml(reqData, key, "MD5");
        String resultStr = requestOnce(refundUrl, str, true);
        Map<String, String> resultDate = xmlToMap(resultStr);
        return resultDate.get("return_code").equals("SUCCESS") && resultDate.get("result_code").equals("SUCCESS");
    }

    /*
     * APP退款
     * */
    public static Boolean appRefund(String orderId, String transactionId,Double totalFee) throws Exception {
        return refund(orderId,openAppId,transactionId,totalFee);
    }

    /*
     * H5退款
     * */
    public static Boolean h5Refund(String orderId, String transactionId,Double totalFee) throws Exception {
        return refund(orderId,publicAppId,transactionId,totalFee);
    }

    /*
    * 转账
    * */
    public static void transfer(String appId) throws Exception {
        Map<String, String> reqData = new HashMap<>();
        reqData.put("appid", appId);
        reqData.put("mch_id", mchId);
        reqData.put("nonce_str", generateNonceStr()); //随机字符串
        reqData.put("partner_trade_no", "test001"); //唯一转账订单号
        reqData.put("openid", "o0enJjk9lg8d-XpMz3WpmpT5AqXQ");//用户openid
        reqData.put("check_name", "NO_CHECK");//NO_CHECK：不校验真实姓名, FORCE_CHECK：强校验真实姓名
        //r_values.Add("re_user_name", "test001"); //收款用户真实姓名。  如果check_name设置为FORCE_CHECK，则必填用户真实姓名
        reqData.put("amount", "1"); //金额，单位分
        reqData.put("desc", "测试");
        String str = generateSignedXml(reqData, key, "MD5");
        String resultStr = requestOnce(transferUrl, str, true);
        Map<String, String> resultDate = xmlToMap(resultStr);
        if (resultDate.get("return_code").equals("SUCCESS") && resultDate.get("result_code").equals("SUCCESS")) {
            // 成功处理
        } else {
            // 错误处理
        }
    }

    /*
     * APP转账
     * */
    public static void appTransfer() throws Exception {
        transfer(openAppId);
    }

    /*
     * H5转账
     * */
    public static void h5Transfer() throws Exception {
        transfer(publicAppId);
    }

    public static void hongbao() throws Exception {
        Map<String, String> reqData = new HashMap<>();
        reqData.put("nonce_str", generateNonceStr()); //随机字符串
        reqData.put("mch_billno", "Testhb001"); ////商户订单号
        reqData.put("mch_id", mchId); ////商户订单号
        reqData.put("wxappid", ""); ////公众账号appid，微信分配的公众账号ID（企业号corpid即为此appId）。在微信开放平台（open.weixin.qq.com）申请的移动应用appid无法使用该接口。
        reqData.put("send_name", ""); ////商户名称
        reqData.put("re_openid", ""); ////用户openid
        reqData.put("total_amount", ""); ////付款金额，单位分
        reqData.put("total_num", "1"); ////红包发放总人数
        reqData.put("wishing", ""); ////红包祝福语
        reqData.put("client_ip", "");
        reqData.put("act_name", ""); ////活动名称
        reqData.put("remark", ""); ////备注信息
        reqData.put("scene_id", ""); ////发放红包使用场景，红包金额大于200或者小于1元时必传 PRODUCT_1:商品促销 PRODUCT_2:抽奖，PRODUCT_3:虚拟物品兑奖
        //PRODUCT_4:企业内部福利 PRODUCT_5:渠道分润 PRODUCT_6:保险回馈 PRODUCT_7:彩票派奖 PRODUCT_8:税务刮奖

        reqData.put("sign_type", "MD5");
        String resultStr = requestOnce(payUrl, generateSignedXml(reqData, key, "MD5"), false);
    }
}
